﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using ZackFlame.IronBrainFuck.JIT;

namespace BrainFuckCompiler
{
    public static class Program
    {
        static readonly MethodInfo get_In, get_Out;

        static Program()
        {
            get_In = typeof(Console).GetProperty("In").GetGetMethod();
            get_Out = typeof(Console).GetProperty("Out").GetGetMethod();
        }

        public static void Main(string[] args)
        {
            string compilerName = Path.GetFileName(
                System.Reflection.Assembly.GetExecutingAssembly().Location);

            if (args.Length < 2)
            {
                Console.WriteLine(string.Format(
                    "IronBrainFuck command-line compiler" +
                    "{0}" +
                    "{0}Usage: {1} <brainFuckCodeFile> <outputPath>" +
                    "{0}e.g. {1} brain.bf c:\brain.exe",
                    Environment.NewLine,
                    compilerName));
            }
            else
            {
                string assemblyName = Path.GetFileNameWithoutExtension(args[1]);
                string directory = Path.GetDirectoryName(args[1]);
                if (directory.Length == 0)
                    directory = ".\\";

                try
                {
                    string brainFuckProgram = File.ReadAllText(args[0]);
                    CompileAndSave(brainFuckProgram, assemblyName, directory);

                    Environment.Exit(0);
                }
                catch (FileNotFoundException ex)
                {
                    Console.Error.WriteLine("{0}: specified file '{1}' not found",
                        compilerName, ex.FileName);
                    Environment.Exit(1);
                }
                catch (IOException ex)
                {
                    Console.Error.WriteLine("{0}: IO error: {1}",
                        compilerName, ex.Message);
                    Environment.Exit(2);
                }
                catch (System.Security.SecurityException ex)
                {
                    Console.Error.WriteLine("{0}: security error: {1}",
                        compilerName, ex.Message);
                    Environment.Exit(3);
                }
                catch (InvalidProgramException ex)
                {
                    Console.Error.WriteLine("{0}: invalid BrainFuck program: {1}",
                        compilerName, ex.Message);
                    Environment.Exit(4);
                }
            }
        }

        public static void CompileAndSave(string brainFuckProgram, string assemblyName, string saveDirectory)
        {
            AppDomain appDomain = AppDomain.CurrentDomain;

            AssemblyName name = new AssemblyName(assemblyName);
            AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(
                name, AssemblyBuilderAccess.RunAndSave, saveDirectory);

            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(
                "BrainFuckProgram", assemblyName + ".exe");

            TypeBuilder typeBuilder = moduleBuilder.DefineType("Program",
                TypeAttributes.Class |
                TypeAttributes.Public |
                TypeAttributes.Sealed);

            MethodBuilder brainMethod = typeBuilder.DefineMethod("Execute",
                MethodAttributes.Public |
                MethodAttributes.Static,
                typeof(UInt16),
                new Type[] 
                {
                    typeof(UInt16[]), typeof(UInt16), // Cells, CellIndex
                    typeof(TextReader), typeof(TextWriter) // Input, Output
                });

            ILGenerator brainIL = brainMethod.GetILGenerator();
            BrainCompiler.GenerateIL(brainIL, brainFuckProgram);

            MethodBuilder mainMethod = typeBuilder.DefineMethod("Main",
                MethodAttributes.HideBySig |
                MethodAttributes.Public |
                MethodAttributes.Static,
                typeof(Int32),
                new Type[] { typeof(string[]) });

            ILGenerator mainIL = mainMethod.GetILGenerator();
            mainIL.Emit(OpCodes.Ldc_I4, (int)UInt16.MaxValue + 1);
            mainIL.Emit(OpCodes.Newarr, typeof(UInt16));
            mainIL.Emit(OpCodes.Ldc_I4_0);
            mainIL.Emit(OpCodes.Call, get_In);
            mainIL.Emit(OpCodes.Call, get_Out);
            mainIL.Emit(OpCodes.Call, brainMethod);
            mainIL.Emit(OpCodes.Ret);

            typeBuilder.CreateType();

            assemblyBuilder.SetEntryPoint(mainMethod, PEFileKinds.ConsoleApplication);
            assemblyBuilder.Save(assemblyName + ".exe",
                PortableExecutableKinds.ILOnly, ImageFileMachine.I386);
        }
    }
}
